#ifndef WORD_TREE_HPP
#define WORD_TREE_HPP


#include <set>
#include <map>
#include <boost/shared_ptr.hpp>
#include <boost/filesystem.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include "mapped_object.h++"
class WritableMappedMemory;
class MappedMemory;


struct PostingData : public MemoryMappedTableHeader {
	uint32_t nb_occurences;
	typedef MappedTable<uint32_t> posting_table_type;
	posting_table_type getPostingTable() {
		return posting_table_type(size_t(nb_occurences),
			*get_pointer_after<PostingData,
				posting_table_type::content_type>());
	}
	posting_table_type getOffsetTable() {
		return posting_table_type(size_t(nb_occurences),
			*getPostingTable().get_pointer_after<posting_table_type::content_type>());
	}
	size_t get_total_size() {
		return sizeof(PostingData) + getPostingTable().get_total_size()
			+ getOffsetTable().get_total_size();
	}
	PostingData(uint32_t nb_occs) : nb_occurences(nb_occs) {}
};

struct Document : public MemoryMappedObject {
	PointerTo<MemoryMappedPascalString> pathbase;
	PointerTo<MemoryMappedPascalString> filename;
	std::string get_path_string(const MappedMemory& m);

};
struct DetailedDataHeader : public MemoryMappedTableHeader {
	uint32_t nb_docs; // documents having this lemma.
	typedef MappedTable<PointerTo<Document> > document_table_type;
	document_table_type getDocumentTable() {
		return document_table_type(size_t(nb_docs),
			*get_pointer_after<DetailedDataHeader,
				document_table_type::content_type>());
	}
	typedef MappedTable<MemoryMappedFloat> tfidf_table_type;
	tfidf_table_type getTFIDFTable() {
		return tfidf_table_type(size_t(nb_docs),
			*getDocumentTable().get_pointer_after<tfidf_table_type::content_type>());
	}
	typedef MappedTable<PointerTo<PostingData> > posting_table_type;
	posting_table_type getPostingTable() {
		return posting_table_type(size_t(nb_docs),
			*getTFIDFTable().get_pointer_after<posting_table_type::content_type>());
	}

	size_t get_total_size() {
		return sizeof (DetailedDataHeader)
			+ getDocumentTable().get_total_size()
			+ getTFIDFTable().get_total_size()
			+ getPostingTable().get_total_size();
	}
};

namespace WordTree {
	struct HashTable;

	struct NodeHeader : MemoryMappedTableHeader {
		typedef PointerTo<NodeHeader> pointer_to_us;
		uint8_t character;
		uint8_t hashtable_size;
		uint8_t hashtable_lower_bound;
		uint8_t hashtable_factor;

		NodeHeader() : character(0), hashtable_size(0),
				hashtable_lower_bound(0),
				hashtable_factor(0) {}

		PointerTo<DetailedDataHeader>& get_detailed_data();
		HashTable getHashTable();
		MappedTable<pointer_to_us>::content_type& getHashTableContent() {
			return *get_pointer_after<NodeHeader, MappedTable<pointer_to_us>::content_type>();
		}
		size_t get_size_of_structure() const {
			return sizeof(NodeHeader) + hashtable_size * sizeof (pointer_to_us)
					+ sizeof (PointerTo<DetailedDataHeader>);
		}
		NodeHeader* getWord(MappedMemory& file, const std::string& str);
	};

	// this thing is not in the file
	struct HashTable : MappedTable<PointerTo<NodeHeader> > {
		NodeHeader& header;
		HashTable(NodeHeader& _header) :
			MappedTable<PointerTo<NodeHeader> >(
					size_t(_header.hashtable_size),
					_header.getHashTableContent()
					), header(_header) {}
	protected:
		// because that function can crash (ie :index/0) if header.hashtable_size is null
		// it is protected.
		// THE CALLER MUST CHECK FOR A NON-EMPTY HASH TABLE !
		size_t get_entry_index(uint8_t character) {
			size_t index = character - header.hashtable_lower_bound;
			index += (index / header.hashtable_size)*header.hashtable_factor;
			return index%header.hashtable_size;
		}
	public:
		PointerTo<NodeHeader>* get_entry(uint8_t character) {
			if (!header.hashtable_size
					|| header.hashtable_lower_bound > character)
				return NULL;
			PointerTo<NodeHeader>* ret;
			ret = getElement(get_entry_index(character));
			return ret;
		}

		bool can_store_without_collision(const std::set<uint8_t>& chars);
		void optimize_node_header_for_chars(const std::set<uint8_t>& chars);
	};
}

class UnserializedDocument {
	// this seems to be like java's File object.
	boost::filesystem::path relative_path;
public:
	std::string get_dirpath() const;
	std::string get_filename() const;
	const boost::filesystem::path& get_path() const {
		return relative_path;
	}
	UnserializedDocument(const boost::filesystem::path& et_path_le_chemin);
};

namespace WordTree {
	class UnserializedNode {
		typedef std::map<char, boost::shared_ptr<UnserializedNode> > map_type;
		map_type childs;
		char character;
	public:
		struct UnserializedMatchingDocument {
			float tf_idf;
			// we don't own it ... should we ?

			struct FullPostings {
			// while using a set might be a better thing,
			// it triple the memory demands. using vectors here
			// halve the total memory consuption of the indexer.
				typedef std::vector<size_t> container_type;
				std::vector<size_t> postings;
				std::vector<size_t> offset_postings;
			};
			struct SerializedPostings {
				PointerTo<PostingData> serialized_ptr;
				size_t nb_occurences;
			};
			typedef boost::variant<FullPostings, SerializedPostings>
				postings_variant_type;
			postings_variant_type postings;
			struct size_getter:public boost::static_visitor<size_t>{
				size_t operator()(const FullPostings& p) {
					return p.postings.size();
				}
				size_t operator()(const SerializedPostings& p) {
					return p.nb_occurences;
				}
			};
			FullPostings& get_full_postings() {
				return boost::get<FullPostings>(postings);
			}
			const FullPostings& get_full_postings() const {
				return boost::get<FullPostings>(postings);
			}
			public:
			void add_postings(size_t index, size_t offset) {
				FullPostings &p(get_full_postings());
				p.postings.push_back(index);
				p.offset_postings.push_back(offset);
			}
			size_t get_nb_occurences() const {
				size_getter visitor;
				return boost::apply_visitor(visitor, postings);
			}
			const FullPostings::container_type& get_postings()const{
				return get_full_postings().postings;
			}
			const FullPostings::container_type& get_offsets() const{
				return get_full_postings().offset_postings;
			}
			void now_serialized(PointerTo<PostingData> ptr) {
				SerializedPostings p;
				p.nb_occurences = get_full_postings(
							).postings.size();
				p.serialized_ptr = ptr;
				postings = p;
			}
			bool is_serialized() const {
				return boost::get<SerializedPostings>(
						&postings);
			}
			PointerTo<PostingData> get_serialized_ptr() const {
				return boost::get<SerializedPostings>(
						postings).serialized_ptr;
			}
		};
	private:
		typedef std::map<UnserializedDocument*, UnserializedMatchingDocument> document_map_type;
		document_map_type matching_documents;
	public:
		typedef document_map_type::value_type matching_document_type;
		UnserializedNode(char _character) : childs(), character(_character),
			matching_documents() {}
		
		// of course, you will not rely on the return type,
		// do you ?
		typedef map_type::value_type childs_type;
		const map_type& get_childs() const { return childs; }
		map_type& get_childs() { return childs; }
		UnserializedMatchingDocument& addDocumentMatch(
				UnserializedDocument* doc,
				size_t position, size_t offset = 0) {
			document_map_type::iterator iter = matching_documents.find(doc);
			if (iter == matching_documents.end())
				matching_documents[doc] = UnserializedMatchingDocument();
			matching_documents[doc].add_postings(position, offset);
			return matching_documents[doc];
		}
		void setDocumentMatchTfIdf(UnserializedDocument* doc, float t) {
			document_map_type::iterator iter;
			iter = matching_documents.find(doc);
			if (iter == matching_documents.end())
				throw std::logic_error("no existing match with"
							" this document");
			matching_documents[doc].tf_idf = t;
		}

		NodeHeader get_optimized_hashtable_parameter() const;

		size_t get_document_count() const {
			return matching_documents.size();
		}
		typedef document_map_type::value_type document_content_type;
		const document_map_type& get_documents() const {
			return matching_documents;
		}
		document_map_type& get_documents() {
			return matching_documents;
		}
		UnserializedNode& create_get_word(const std::string& str);
		void print_representation(std::ostream& o, size_t recursion=0);
	};
}

#endif
